package population;

import iteration.RecursiveAccess;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import combinations.Combinations;
import combinations.hierarchical.HierarchicalAttributeCombinations;

import attributes.AttributeGraphPlusDictionary;
import attributes.HierarchicalAttributes;
import attributes.HierarchicalAttributesBundle;
import attributes.HierarchicalAttributesImporter;

import trees.AttributeNode;
import utils.Util;

import coalition.Coalition;
import coalition.CoalitionFormation;
import coalition.CoalitionWithInfo;
import coalition.CoalitionWithInfoInterface;
import coalition.filters.HierarchicalCoalitionFilter;
import coalition.scoring.AgentAltruism;
import coalition.scoring.AltruismScore;
import coalition.scoring.CoalitionScore;

import multiarray.MultiArray;


public class Population implements PopulationInterface {

	final float ALPHA = 0.5f;
	
	//multidimensional array instantiated with PopulationSector's
	//each sector is a group of people sharing all ascriptive attributes
	private MultiArray<PopulationSector> multiArray;
	
	MultiArray<MultiArray<Float>> altruismArrays;
	
	private CoalitionScore coalitionScore;
	
	List<AgentAltruism> agentAltruismList;
	
	int[] dimensionSizes;
	int maximumDisjunctiveSize = -1;
	boolean negationsAllowed = true;
	
	private CoalitionFormation coalitionFormation;
	
	private HierarchicalAttributesBundle hierarchicalAttributesBundle =
		new HierarchicalAttributesBundle();
	private HashMap<Integer, String> attributeNames = new HashMap<Integer, String>();
	private HashMap<Integer, List<String>> categoryNamesMap = new HashMap<Integer, List<String>>();
	
	public void setCoalitionFormation(CoalitionFormation coalitionFormation) {
		this.coalitionFormation = coalitionFormation;
	}

	public List<AgentAltruism> getAgentAltruismList() {
		return agentAltruismList;
	}

	//ArrayList<HashMap<Integer, Integer>> childParentPairs;
	
	public Population(int[] dimensionSizes){
		this.dimensionSizes = dimensionSizes;
		multiArray = new MultiArray<PopulationSector>(dimensionSizes);
		coalitionFormation = new CoalitionFormation(this);
	}
	
	public int[] getDimensionSizes() {
		return dimensionSizes;
	}

	public int getMaximumDisjunctiveSize() {
		return maximumDisjunctiveSize;
	}

	public boolean isNegationsAllowed() {
		return negationsAllowed;
	}

	public static PopulationInterface getNewPopulationInterface(int[] dimensionSizes){
		Population population = new Population(dimensionSizes);
		population.initializeSectorsToZero();
		//population.initializeChildParentPairHashMaps();
		population.coalitionFormation = new CoalitionFormation(population);
		return population;
	}
	
	/*
	public void setChildParentRelationship(int attributeIndex, int childIndex, int parentIndex){
		if(attributeIndex >= dimensionSizes.length)
			throw new IllegalArgumentException("illegal attribute index for child-parent pair");
		if(childIndex >= dimensionSizes[childIndex])
			throw new IllegalArgumentException("illegal index for child in child-parent pair");
		if(parentIndex >= dimensionSizes[childIndex])
			throw new IllegalArgumentException("illegal index for parent in child-parent pair");	
		childParentPairs.get(attributeIndex).put(childIndex, parentIndex);
	}
	*/
	
	public void setMaximumDisjunctiveSize(int size){
		if(size <= 0)
			throw new IllegalArgumentException("illegal size for max disjunctive limit");
		this.maximumDisjunctiveSize = size;
	}
	
	public void setMaximumDisjunctiveSize(int size, boolean negationsAllowed){
		if(size <= 0)
			throw new IllegalArgumentException("illegal size for max disjunctive limit");
		this.maximumDisjunctiveSize = size;
		this.negationsAllowed = negationsAllowed;
	}	
	
	/*
	public void initializeChildParentPairHashMaps(){
		childParentPairs = new ArrayList<HashMap<Integer, Integer>>(dimensionSizes.length);
		for(int i = 0; i < dimensionSizes.length; i++){
			childParentPairs.set(i, new HashMap<Integer, Integer>());
		}
	}	
	*/
	
	public void initializeSectorsToZero(){
		int[] indices = Util.getIndexArrayWithOneValue(dimensionSizes.length, 0, -1);
		List<int[]> allSectorIndices = RecursiveAccess.generateAllPermutations(indices, dimensionSizes);
		for(int[] sectorIndices : allSectorIndices){
			PopulationSector populationSector = new PopulationSector(0, 1.0f);
			setSector(sectorIndices, populationSector);
		}
	}
	
	public float getTotalModulatedSize(){
		int[] indices = Util.getIndexArrayWithOneValue(dimensionSizes.length, 0, -1);
		List<int[]> allSectorIndices = RecursiveAccess.generateAllPermutations(indices, dimensionSizes);
		float modulatedSize = 0;
		for(int[] sectorIndices : allSectorIndices){
			PopulationSector populationSector = getSector(sectorIndices);
			modulatedSize += populationSector.getPower() * populationSector.getSize();
		}
		return modulatedSize;
	}	
	
	/* (non-Javadoc)
	 * @see PopulationInterface#registerAgentInSector(int[])
	 */
	public void registerAgentInSector(int[] sectorIndices){
		setSectorPopulation(sectorIndices, getSectorPopulationSize(sectorIndices) + 1);
	}	
	
	public void setSector(int[] sectorIndices, PopulationSector populationSector){
		multiArray.setElementAtIndex(sectorIndices, populationSector);
	}
	
	/* (non-Javadoc)
	 * @see PopulationInterface#setSectorPopulation(int[], int)
	 */
	public void setSectorPopulation(int[] sectorIndices, int populationSize){
		PopulationSector oldPopulationSector = multiArray.getElementAtIndex(sectorIndices);
		PopulationSector newPopulationSector;
		if(oldPopulationSector != null){
			newPopulationSector = new PopulationSector(populationSize, 
			oldPopulationSector.getPower()		
			);
		} else {
			newPopulationSector = new PopulationSector(populationSize, 1.0f);				
		}
		multiArray.setElementAtIndex(sectorIndices, newPopulationSector);
	}	
	
	/* (non-Javadoc)
	 * @see PopulationInterface#setSectorPower(int[], float)
	 */
	public void setSectorPower(int[] sectorIndices, float power){
		PopulationSector oldPopulationSector = multiArray.getElementAtIndex(sectorIndices);
		PopulationSector newPopulationSector;
		if(oldPopulationSector != null){
			newPopulationSector = new PopulationSector(oldPopulationSector.getSize(), power);
		} else {
			newPopulationSector = new PopulationSector(0, power);				
		}
		multiArray.setElementAtIndex(sectorIndices, newPopulationSector);
	}		
	
	public PopulationSector getSector(int[] sectorIndices){
		return multiArray.getElementAtIndex(sectorIndices);
	}
	
	/* (non-Javadoc)
	 * @see PopulationInterface#getSectorPopulationSize(int[])
	 */
	public int getSectorPopulationSize(int[] sectorIndices){
		return multiArray.getElementAtIndex(sectorIndices).getSize();
	}
	
	public float getSectorModulatedPopulationSize(int[] sectorIndices){
		return multiArray.getElementAtIndex(sectorIndices).getSize()*
		multiArray.getElementAtIndex(sectorIndices).getPower();
	}	
	
	/* (non-Javadoc)
	 * @see PopulationInterface#getSectorPower(int[])
	 */
	public float getSectorPower(int[] sectorIndices){
		return multiArray.getElementAtIndex(sectorIndices).getPower();
	}	
	
	//coalition claculations can only be done if all sectors are instantiated
	public boolean checkAllSectorsFilled(){
		for(int i = 0; i < multiArray.getNumObjects(); i++){
			if(multiArray.getElementAtRawIndex(i) == null)
				return false;
		}
		return true;
	}

	@Override
	public CoalitionWithInfoInterface getPreferredCoalition(
			int[] querySectorIndices) {
		return coalitionFormation.getPreferredCoalition(querySectorIndices);
	}

	@Override
	public CoalitionWithInfoInterface getWinningCoalition() {
		return coalitionFormation.getWinningCoalition();
	}
	
	private float getCoalitionScore(){
		return 0;
	}
	
	public List<CoalitionWithInfo> computeCoalitions(){
		return coalitionFormation.computeCoalitions();
	}

	@Override
	public CoalitionWithInfoInterface createCustomCoalition(List<int[]> sectors) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public CoalitionWithInfoInterface getWinningCoalitionMinusSectors(
			List<int[]> excludedSectors) {
		List<CoalitionWithInfo> allCoalitions = computeCoalitions();
		List<CoalitionWithInfo> filteredCoalitions =
			filterOutCoalitionsInExcludedSectors(excludedSectors, allCoalitions);
		return filteredCoalitions.get(0);
	}
	
	//public void setExcludedSectors(List<int[]> excludedSectors){
	//	this.excludedSectors = excludedSectors;
	//}
	
	private List<CoalitionWithInfo> filterOutCoalitionsInExcludedSectors(List<int[]> excludedSectors, List<CoalitionWithInfo> allCoalitions){
		List<CoalitionWithInfo> filteredCoalitions = new ArrayList<CoalitionWithInfo>();
		for(CoalitionWithInfo coalitionWithInfo : filteredCoalitions){
			if(!coalitionIncludesExcludedSectors(excludedSectors, coalitionWithInfo.getCoalition()))
				filteredCoalitions.add(coalitionWithInfo);
		}
		return filteredCoalitions;
	}
	
	private boolean coalitionIncludesExcludedSectors(List<int[]> excludedSectors, Coalition coalition){
		List<int[]> sectorsInvolved = coalition.getSectorsInvolved();
		for(int[] sector1: sectorsInvolved){
			for(int[] sector2: excludedSectors){
				if(Arrays.equals(sector1, sector2))
					return true;
			}
		}
		return false;
	}
	
	private void convertAltruismArrays(){
		altruismArrays = new MultiArray<MultiArray<Float>>(dimensionSizes);
		for(AgentAltruism agentAltruism : agentAltruismList){
			int[] indices = new int[dimensionSizes.length];
			for(int i = 0; i < indices.length; i++) indices[i] = -1;
			//List<int[]> targetIndicesList = RecursiveAccess.generateAllPermutations(indices, dimensionSizes);
			MultiArray<Float> altruismArray = agentAltruism.getAgentAltruismArray();
			altruismArrays.setElementAtIndex(agentAltruism.getAgentAttributes(), altruismArray);
			//for(int[] targetIndices : targetIndicesList){
			//	altruismArray.setElementAtIndex(targetIndices, );
			//}
		}
	}

	@Override
	public void setAltruismArrays(List<AgentAltruism> agentAltruismList) {
		this.agentAltruismList = agentAltruismList;
		convertAltruismArrays();
		coalitionScore = new AltruismScore(this,
		altruismArrays,
		ALPHA);
	}
	
	@Override
	public void setAltruismArraysGlobally(MultiArray<MultiArray<Float>> altruismArrays){
		this.altruismArrays = altruismArrays;
		coalitionScore = new AltruismScore(this,
				altruismArrays,
				ALPHA);		
	}

	@Override
	public void setAttributeAndCategoryNames(int index, String attributeName,
			String[] categoryNames) {
		attributeNames.put(index, attributeName);
		List<String> categoryNamesList = Arrays.asList(categoryNames);
		categoryNamesMap.put(index, categoryNamesList);
	}

	@Override
	public void setHierarchicalStructureForAtttribute(
			int attributeIndex,
			HashMap<Integer, Integer> links, 
			HashMap<Integer, String> names,
			List<Integer> roots
			//List<Integer> leafs
			) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public List<AttributeNode> getAttributeHierarchy(int index) {
		ArrayList<AttributeNode> trees = new ArrayList<AttributeNode>(
				hierarchicalAttributesBundle.getAttributeForests(index).getTrees().values()
				);
		return trees;
	}

	@Override
	public List<CoalitionWithInfoInterface> getRecursivelyResidualCoalitions() {
		List<CoalitionWithInfo> coalitions = computeCoalitions();
		final HashMap<CoalitionWithInfo, Float> coalitionAltruismScores =
			new HashMap<CoalitionWithInfo, Float>();
		for(CoalitionWithInfo coalitionWithInfo : coalitions){
			float score = coalitionScore.getCoalitionScore(coalitionWithInfo);
			coalitionAltruismScores.put(coalitionWithInfo, score);
		}
		Collections.sort(coalitions, new Comparator<CoalitionWithInfo>(){

			@Override
			public int compare(CoalitionWithInfo o1, CoalitionWithInfo o2) {
				float score1 = coalitionAltruismScores.get(o1);
				float score2 = coalitionAltruismScores.get(o2);
				//java.util.Collections.sort convention
				//Returns a negative integer, zero, or a positive integer as the 
				//first argument is less than, equal to, or greater than the second.
				if(score1 < score2) return -1;
				if(score1 > score2) return 1;
				else return 0;
			}
			
		}
		);
		
		//we want the coalitions by descending order of their scores
		//but the normal sort function sorts by ascending order
		Collections.reverse(coalitions);
		
		List<int[]> excludedSectors = new ArrayList<int[]>();
		List<CoalitionWithInfoInterface> result = new ArrayList<CoalitionWithInfoInterface>();
		for(CoalitionWithInfo coalitionWithInfo : coalitions){
			//coalitionIncludesExcludedSectors(List<int[]> excludedSectors, Coalition coalition)
			//filterOutCoalitionsInExcludedSectors(List<int[]> excludedSectors, List<CoalitionWithInfo> allCoalitions)
			//coalitionIncludesExcludedSectors(List<int[]> excludedSectors, Coalition coalition)
			if(coalitionIncludesExcludedSectors(excludedSectors, coalitionWithInfo.getCoalition()))
				continue;
			result.add(coalitionWithInfo);
			List<int[]> newExcludedSectors = coalitionWithInfo.getCoalition().getSectorsInvolved();
			excludedSectors.addAll(newExcludedSectors);
		}
		return result;
	}

	@Override
	public void setHierarchicalStructureForAtttribute(int index, String attributeFileBlock) {
		/*
		AttributeGraphPlusDictionary attributeGraphPlusDictionary;
		attributeGraphPlusDictionary = HierarchicalAttributesImporter.
		getChildParentPairsFromString(attributeFileBlock);
		HierarchicalAttributes attributes = new HierarchicalAttributes(attributeGraphPlusDictionary);
		
		*/		
		
		
		String name = attributeNames.get(index);
		if(name == null) 
			throw new IllegalStateException("trying to specify hierarchy for attribute with no name");
		hierarchicalAttributesBundle.addAttributeInfo(index, name, attributeFileBlock);
		HierarchicalAttributes attributes = 
			hierarchicalAttributesBundle.getAttributeForests(index);
		HierarchicalCoalitionFilter filter = new HierarchicalCoalitionFilter(attributes);
		Combinations combinationsScheme = new HierarchicalAttributeCombinations(filter);
		coalitionFormation.setCombinationsScheme(combinationsScheme, index);	
		
	}

	@Override
	public String getAttributeName(int index) {
		return attributeNames.get(index);
	}

	@Override
	public List<String> getCategoryNamesForAttribute(int index) {
		Collection<String> names = 
			hierarchicalAttributesBundle.getAttributeForests(index).getDictionary().values();
		return new ArrayList<String>(names);
	}	
	
}


